import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.StringTokenizer;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
public class FindClass {

	/** directory to start search */
	private String searchDir;

	/** pattern of class name to search */
	private String searchPattern;

	/** search recursively - defaults to false */
	private boolean recurse;

	/**
	 * @param args
	 */
	public static void main(String[] args) {

		long now = System.currentTimeMillis();
		if (args.length != 2) {
			usage();
			return;
		}

		SearchManager manager = SearchManager.getInstance();

		String start = args[0];
		String lookfor = args[1];
		lookfor = Util.convertClassNameToFileName(lookfor);
		String rec = System.getProperty("recurse");
		boolean recurse = false;
		if (rec != null) {
			recurse = rec.equalsIgnoreCase("true") ? true : false;
		}

		Logger.log("Searching for " + lookfor + " from " + start
				+ " with recurse = " + recurse, LogLevel.INFO);

		SearchTask primary = new SearchTask(start, lookfor, recurse);
		manager.search(primary);
		manager.stop();
		Logger.log("Completed in " + (System.currentTimeMillis() - now) + "ms",
				LogLevel.INFO);
	}

	public static void usage() {
		/**
		 * java -Dmax.thread=<int> -Drecurse=<true|false> -Dtask.poll.freq=<ms>
		 * FindClass <search_dir> <search_pattern>
		 */
		String message = "java -Dmax.thread=<int> -Drecurse=<int>	FindClass <search_dir> <search_pattern>";
		Logger.log(message, LogLevel.INFO);
	}
}

class SearchManager {
	private final ExecutorService service;
	private static SearchManager manager = new SearchManager();
	private List<SearchTask> tasks = new ArrayList<SearchTask>();

	private SearchManager() {
		int threadmaxcount;
		String count = System.getProperty("max.thread");
		try {
			threadmaxcount = Integer.parseInt(count);
		} catch (NumberFormatException nfe) {
			Logger.log("Invalid max thread count : " + count, LogLevel.INFO);
			threadmaxcount = 10;
		}

		service = Executors.newFixedThreadPool(threadmaxcount);
		Logger.log("thread pool initilized with " + threadmaxcount,
				LogLevel.DEBUG);
	}

	public static SearchManager getInstance() {
		return manager;
	}

	public void search(SearchTask task) {
		tasks.add(task);
		service.execute(task);
	}

	public void stop() {
		boolean stop = false;
		boolean status;
		SearchTask task = null;

		int taskpoller = 1;
		String frequency = System.getProperty("task.poll.freq");
		try {
			taskpoller = Integer.parseInt(frequency);
		} catch (NumberFormatException nfe) {
			Logger.log("Invalid task.poll.freq" + frequency, LogLevel.ERROR);
		}
		Logger.log("task.poll.freq set to " + taskpoller + " ms", LogLevel.INFO);
		while (!stop) {
			try {
				Thread.sleep(taskpoller);
			} catch (InterruptedException e) {
			}
			int size = tasks.size();
			status = true;// dummy assignment to start the loop
			for (int index = 0; (index < size) && status; index++) {
				task = tasks.get(index);
				if (index == 0) {
					status = task.isComplete();
				} else {
					status = status && task.isComplete();
				}
			}

			stop = status ? true : false;
		}

		Logger.log("Shutting down ", LogLevel.DEBUG);
		this.service.shutdown();
	}
}

class SearchTask implements Runnable {
	private String root;
	private String pattern;
	private boolean recurse;
	private boolean isComplete;

	SearchTask(String dir, String pattern, boolean recurse) {
		this.root = dir;
		this.pattern = pattern;
		this.recurse = recurse;
	}

	public void run() {
		Logger.log("Starting " + this, LogLevel.DEBUG);
		File dir = new File(root);
		File[] files = dir.listFiles(new FilenameFilter() {
			public boolean accept(File dir, String name) {
				boolean accept = true;

				if (recurse && new File(dir, name).isDirectory()) {
					accept = true;
				} else {
					accept = (new File(dir, name).isFile() && (name
							.endsWith(".jar") || name.endsWith(".zip"))) ? true
							: false;
				}
				Logger.log("Accept " + new File(dir, name).getAbsolutePath()
						+ " : " + accept, LogLevel.DEBUG);
				return accept;
			}
		});
		File thisfile = null;
		for (int i = 0; i < files.length; i++) {
			thisfile = files[i];
			if (thisfile.isFile()) {
				// Search archive
				Util.matchInZip(thisfile, pattern);
			} else if (thisfile.isDirectory() && recurse) {
				SearchTask task = new SearchTask(thisfile.getAbsolutePath(),
						this.pattern, this.recurse);
				SearchManager.getInstance().search(task);
			}
		}

		isComplete = true;
		Logger.log("Completed " + this, LogLevel.DEBUG);
	}

	public String toString() {
		return "Searching " + this.root + " for " + this.pattern
				+ " with recurse = " + this.recurse;
	}

	public boolean isComplete() {
		return isComplete;
	}
}

class Util {
	private static final String pathSep = "/";// File.separator;

	public static boolean isArchive(File f) {
		boolean isArchive = false;

		return isArchive;
	}

	public static String convertClassNameToFileName(String classname) {
		String filename = "";
		if (classname != null) {
			if (classname.endsWith(".class")) {
				classname = classname.substring(0, classname.indexOf(".class"));

				if (classname.indexOf(".") != -1) {
					StringTokenizer tokenizer = new StringTokenizer(classname,
							".");
					while (tokenizer.hasMoreElements()) {
						if (filename == "") {
							filename = tokenizer.nextToken();
						} else {
							filename = filename + pathSep
									+ tokenizer.nextToken();
						}
					}
				}
				filename += ".class";
			}else{
				filename = classname;
			}
		}
		return filename;
	}

	public static void matchInZip(File zipfile, String pattern) {
		ZipFile file = null;

		try {
			file = new ZipFile(zipfile);
			Enumeration<? extends ZipEntry> entries = file.entries();
			String entryname = "";
			while (entries.hasMoreElements()) {
				entryname = entries.nextElement().getName();
				if (entryname.contains(pattern)) {
					Logger.log(
							"Found " + entryname + " in "
									+ zipfile.getAbsolutePath(), LogLevel.INFO);
				}
			}
		} catch (IOException e) {
			Logger.log("Error while opening " + zipfile.getAbsolutePath(),
					LogLevel.ERROR);
		}
	}
}

class Logger {

	public static void log(String message, LogLevel level) {
		if (level == LogLevel.INFO || level == LogLevel.ERROR) {
			System.out.println(message);
		} else if (level == LogLevel.DEBUG) {
			// System.out.println(message);
		}

	}
}

enum LogLevel {
	DEBUG, INFO, ERROR
}